<?php

// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2025-04-25 10:54:44
// |@----------------------------------------------------------------------
// |@LastEditTime : 2025-04-27 05:36:35
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : 插件基类
// |@----------------------------------------------------------------------
// |@FilePath     : Addons.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2025 http://www.ladmin.cn   All rights reserved.
// ------------------------------------------------------------------------

declare(strict_types=1);

namespace think;

use think\App;
use think\helper\Str;
use think\facade\View;
use RuntimeException;

/**
 * 插件基类
 *
 * 提供插件基础功能，包括：
 * 1. 插件信息管理
 * 2. 配置管理
 * 3. 视图渲染
 * 4. 安装卸载
 *
 * @example
 * ```php
 * class Demo extends Addons
 * {
 *     public function install()
 *     {
 *         // 安装逻辑
 *         return true;
 *     }
 *
 *     public function uninstall()
 *     {
 *         // 卸载逻辑
 *         return true;
 *     }
 * }
 * ```
 */
abstract class Addons
{
    /**
     * 缓存键前缀
     */
    private const CACHE_PREFIX = 'addons_';

    /**
     * app 容器
     * @var App
     */
    protected App $app;

    /**
     * 请求对象
     * @var \think\Request
     */
    protected $request;

    /**
     * 当前插件标识
     * @var string
     */
    protected string $name;

    /**
     * 插件路径
     * @var string
     */
    protected string $addon_path;

    /**
     * 视图模型
     * @var \think\View
     */
    protected $view;

    /**
     * 插件配置键名
     * @var string
     */
    protected string $addon_config;

    /**
     * 插件信息键名
     * @var string
     */
    protected string $addon_info;

    /**
     * 插件构造函数
     *
     * 初始化插件基本信息，包括：
     * 1. 应用实例
     * 2. 请求对象
     * 3. 插件标识
     * 4. 插件路径
     * 5. 视图配置
     *
     * @param App $app 应用实例
     * @throws RuntimeException 初始化失败时抛出异常
     */
    public function __construct(App $app)
    {
        try {
            $this->app = $app;
            $this->request = $app->request;
            $this->name = $this->getName();
            $this->addon_path = $app->addons->getAddonsPath() . $this->name . DIRECTORY_SEPARATOR;
            $this->addon_config = "addon_{$this->name}_config";
            $this->addon_info = "addon_{$this->name}_info";
            $this->initView();
        } catch (\Exception $e) {
            $app->log->error('Addons initialization failed: ' . $e->getMessage());
            throw new RuntimeException('Addons initialization failed: ' . $e->getMessage());
        }
    }

    /**
     * 初始化视图
     *
     * 配置插件视图路径和引擎
     */
    private function initView(): void
    {
        $this->view = clone View::engine('Think');
        $this->view->config([
            'view_path' => $this->addon_path . 'view' . DIRECTORY_SEPARATOR
        ]);
    }

    /**
     * 获取插件标识
     *
     * 从类名中解析插件标识，并设置到请求对象中
     *
     * @return string 插件标识
     */
    final protected function getName(): string
    {
        $class = get_class($this);
        list(, $name, ) = explode('\\', $class);
        $this->request->addon = $name;
        return $name;
    }

    /**
     * 加载模板输出
     *
     * 渲染插件模板文件
     *
     * @example
     * ```php
     * // 渲染默认模板
     * $this->fetch();
     *
     * // 渲染指定模板
     * $this->fetch('index/index');
     *
     * // 渲染模板并传递变量
     * $this->fetch('index/index', ['title' => 'Hello']);
     * ```
     *
     * @param string $template 模板文件名
     * @param array $vars 模板输出变量
     * @return string 渲染后的内容
     * @throws \think\Exception
     */
    protected function fetch(string $template = '', array $vars = []): mixed
    {
        return $this->view->fetch($template, $vars);
    }

    /**
     * 渲染内容输出
     *
     * 直接渲染模板内容
     *
     * @example
     * ```php
     * // 渲染内容
     * $this->display('Hello {$name}', ['name' => 'World']);
     * ```
     *
     * @param string $content 模板内容
     * @param array $vars 模板输出变量
     * @return string 渲染后的内容
     */
    protected function display(string $content = '', array $vars = []): mixed
    {
        return $this->view->display($content, $vars);
    }

    /**
     * 模板变量赋值
     *
     * 设置模板变量
     *
     * @example
     * ```php
     * // 设置单个变量
     * $this->assign('name', 'World');
     *
     * // 设置多个变量
     * $this->assign([
     *     'name' => 'World',
     *     'age' => 18
     * ]);
     * ```
     *
     * @param mixed $name 要显示的模板变量
     * @param mixed $value 变量的值
     * @return $this
     */
    protected function assign($name, $value = ''): self
    {
        $this->view->assign([$name => $value]);
        return $this;
    }

    /**
     * 初始化模板引擎
     *
     * 设置模板引擎
     *
     * @example
     * ```php
     * // 设置模板引擎
     * $this->engine('Think');
     * ```
     *
     * @param array|string $engine 引擎参数
     * @return $this
     */
    protected function engine($engine): self
    {
        $this->view->engine($engine);
        return $this;
    }

    /**
     * 获取插件信息
     *
     * 获取插件的配置信息，包括：
     * 1. 插件名称
     * 2. 插件版本
     * 3. 插件作者
     * 4. 插件描述
     *
     * @example
     * ```php
     * // 获取插件信息
     * $info = $this->getInfo();
     * // 返回示例：
     * // [
     * //     'name' => 'demo',
     * //     'title' => '示例插件',
     * //     'version' => '1.0.0',
     * //     'author' => 'Jarmin'
     * // ]
     * ```
     *
     * @return array 插件信息
     * @throws RuntimeException 信息加载失败时抛出异常
     */
    final public function getInfo(): array
    {
        try {
            $cacheKey = self::CACHE_PREFIX . $this->addon_info;
            $info = $this->app->cache->get($cacheKey);

            if ($info === null) {
                $info = $this->app->config->get($this->addon_info, []);
                if (empty($info)) {
                    $info = $this->loadInfoFromFile();
                    $this->app->config->set($info, $this->addon_info);
                    $this->app->cache->set($cacheKey, $info);
                }
            }

            return $info;
        } catch (\Exception $e) {
            $this->app->log->error('Addons info load failed: ' . $e->getMessage());
            throw new RuntimeException('Addons info load failed: ' . $e->getMessage());
        }
    }

    /**
     * 从文件加载插件信息
     *
     * 从插件目录下的 info.ini 文件加载插件信息
     *
     * @return array 插件信息
     */
    private function loadInfoFromFile(): array
    {
        $info = $this->info ?? [];
        $info_file = $this->addon_path . 'info.ini';

        if (is_file($info_file)) {
            $_info = parse_ini_file($info_file, true, INI_SCANNER_TYPED) ?: [];
            $_info['url'] = addons_url();
            $info = array_merge($_info, $info);
        }

        return $info;
    }

    /**
     * 获取配置信息
     *
     * 获取插件的配置信息，支持获取完整配置或仅获取值
     *
     * @example
     * ```php
     * // 获取配置值
     * $config = $this->getConfig();
     * // 返回示例：
     * // [
     * //     'key1' => 'value1',
     * //     'key2' => 'value2'
     * // ]
     *
     * // 获取完整配置
     * $config = $this->getConfig(true);
     * // 返回示例：
     * // [
     * //     'key1' => [
     * //         'type' => 'text',
     * //         'value' => 'value1'
     * //     ]
     * // ]
     * ```
     *
     * @param bool $type 是否获取完整配置
     * @return array 配置信息
     * @throws RuntimeException 配置加载失败时抛出异常
     */
    final public function getConfig(bool $type = false): array
    {
        try {
            $cacheKey = self::CACHE_PREFIX . $this->addon_config . ($type ? '_full' : '');
            $config = $this->app->cache->get($cacheKey);

            if ($config === null) {
                $config = $this->app->config->get($this->addon_config, []);
                if (empty($config)) {
                    $config = $this->loadConfigFromFile($type);
                    $this->app->config->set($config, $this->addon_config);
                    $this->app->cache->set($cacheKey, $config);
                }
            }

            return $config;
        } catch (\Exception $e) {
            $this->app->log->error('Addons config load failed: ' . $e->getMessage());
            throw new RuntimeException('Addons config load failed: ' . $e->getMessage());
        }
    }

    /**
     * 从文件加载配置信息
     *
     * 从插件目录下的 config.php 文件加载配置信息
     *
     * @param bool $type 是否获取完整配置
     * @return array 配置信息
     */
    private function loadConfigFromFile(bool $type): array
    {
        $config = [];
        $config_file = $this->addon_path . 'config.php';

        if (is_file($config_file)) {
            $temp_arr = (array)include $config_file;
            if ($type) {
                return $temp_arr;
            }
            foreach ($temp_arr as $key => $value) {
                $config[$key] = $value['value'];
            }
        }

        return $config;
    }

    /**
     * 魔术方法 __get
     *
     * 获取插件服务或仓储类实例
     *
     * @param string $name 属性名
     * @return mixed 服务或仓储类实例
     */
    final public function __get(string $name): mixed
    {
        if (preg_match('/^service(\w+)$/i', $name, $matches)) {
            $className = Str::studly($matches[1]);
            return $this->loadService($className);
        }
        if (preg_match('/^repository(\w+)$/i', $name, $matches)) {
            $className = Str::studly($matches[1]);
            return $this->loadRepository($className);
        }
    }

    /**
     * 加载服务类
     *
     * 按照以下顺序查找服务类：
     * 1. think\admin\service 命名空间下
     * 2. 应用自定义服务类
     *
     * @param string $className 服务类名
     * @return mixed 服务类实例
     * @throws \think\exception\HttpResponseException 服务类不存在时抛出异常
     */
    protected function loadService(string $className)
    {
        $services = [
            "\\addons\\{$this->name}\\service\\{$className}",
            "\\think\\admin\\service\\{$className}",
            $this->app->parseClass('service', $className)
        ];
        foreach ($services as $class) {
            if (class_exists($class)) {
                return $this->app->make($class);
            }
        }
        $this->error(500, lang('addons_service_not_found', ['addon' => $this->name, 'class' => $className]));
    }

    /**
     * 加载仓储类
     *
     * 按照以下顺序查找仓储类：
     * 1. think\admin\repository 命名空间下
     * 2. 应用自定义仓储类
     *
     * @param string $className 仓储类名
     * @return Repository
     * @throws HttpResponseException 当仓储类不存在时抛出异常
     */
    protected function loadRepository(string $className): mixed
    {
        $repositorys = [
            "\\addons\\{$this->name}\\repository\\{$className}",
            "\\think\\admin\\repository\\{$className}",
            $this->app->parseClass('repository', $className),
        ];

        foreach ($repositorys as $class) {
            if (class_exists($class)) {
                return $this->app->make($class);
            }
        }
        throw_response(500, lang('addons_repository_not_found', ['addon' => $this->name, 'class' => $className]));
    }

    /**
     * 安装插件
     *
     * 插件安装时需要实现的方法，用于：
     * 1. 创建数据库表
     * 2. 初始化配置
     * 3. 创建目录
     * 4. 其他安装操作
     *
     * @example
     * ```php
     * public function install(): bool
     * {
     *     // 创建数据库表
     *     $sql = "CREATE TABLE IF NOT EXISTS `{$this->name}` ...";
     *     $this->app->db->execute($sql);
     *
     *     // 初始化配置
     *     $config = ['key' => 'value'];
     *     $this->app->config->set($config, $this->addon_config);
     *
     *     return true;
     * }
     * ```
     *
     * @return bool 安装结果
     */
    abstract public function install(): bool;

    /**
     * 卸载插件
     *
     * 插件卸载时需要实现的方法，用于：
     * 1. 删除数据库表
     * 2. 清理配置
     * 3. 删除文件
     * 4. 其他清理操作
     *
     * @example
     * ```php
     * public function uninstall(): bool
     * {
     *     // 删除数据库表
     *     $sql = "DROP TABLE IF EXISTS `{$this->name}`";
     *     $this->app->db->execute($sql);
     *
     *     // 清理配置
     *     $this->app->config->delete($this->addon_config);
     *
     *     return true;
     * }
     * ```
     *
     * @return bool 卸载结果
     */
    abstract public function uninstall(): bool;
}
